/*
 * ISCP.java
 *
 * Created on January 22, 2007, 7:17 PM
 *
 * Inter Snake Communication Protocol [ ISCP ]
 *
 */

package net.sf.iSnake.core;

import java.awt.Color;
import java.net.InetAddress;
import java.util.Calendar;
import java.util.Vector;
import net.sf.iSnake.configuration.iSnakeConfiguration;

/**
 * Communication protocol used by remote players to exchange data
 * @author Jitendra
 *
 * The communication of iSnakeClient with the iSnakeServer will take place in one of the two possible modes
 * 1. PRE mode:     The mode before actual start of the game.
 *                  (connect to server -> send player preferences -> receive server configuration -> ..... etc
 *                  ONLY ONE THREAD WILL BE CREATED WHICH WILL BE USED FOR BOTH SEND/RECEIVE
 *
 * 2. POST mode:    The mode when the actual game starts
 *                  (send dx,dy -> send score -> send lives -> ...... etc
 *                  TWO THREADS NAMELY (SEND thread, RECEIVE thread) will be created
 *
 * The use of this class will work as follows
 *  1. Join a game created by a remote server
 *          ISCP iscp = new ISCP("localhost",9669);
 *          premodeISCPpacket joinGamePacket = new premodeISCPpacket(iscp.PRE_CMD_JoinGame);
 *          // No parameters of the packet need to be sent as the PRE_CMD_JoinGame command does not require any parameter
 *          iscp.encodeAndSend_preModeISCPpacket(joinGamePacket);   // send the packet to server
 *          premodeISCPpacket reply_premodeISCPpacket = iscp.receiveAndDecode_preModeISCPpacket();
 *          if(reply_premodeISCPpacket.getPremodeCommand() == iscp.PRE_CMD_ERRJoinGame)
 *
 *
 */
public class ISCP extends Thread {
    
    private ClientNetworkInterface clientNetworkInterface;
    private iSnakeConfiguration conf;
    private GameController gameController;
    
    private boolean stopISCP;
    
    private Object objectReceived;
    
    private boolean premode;
    private boolean postmode;
    
    private premodeISCPpacket premodePacket;
    private postmodeISCPpacketForSend postmodeSendPacket;
    private postmodeISCPpacketForMulticast postmodeMulticastPacket;
    private boolean packetReceived;
    
    /**
     * Command for PRE Mode session
     */
    public static final Short PRE_CMD_LocalPlayerPreference     = 1;
    public static final Short PRE_CMD_ChatMessage               = 2;
    public static final Short PRE_CMD_SnakeStartCoordinate      = 3;    // starting coordinate of snake head
    public static final Short PRE_CMD_GameSpeed                 = 4;
    public static final Short PRE_CMD_RemotePlayerPreference    = 5;
    public static final Short PRE_CMD_JoinGame                  = 6;
    public static final Short PRE_CMD_OKJoinGame                = 7;
    public static final Short PRE_CMD_ERRJoinGame               = 8;
    public static final Short PRE_CMD_WallCoordinate            = 9;
    public static final Short PRE_CMD_PlayerThrownOut           = 10;
    
    // syncronization pulses
    public static final Short PRE_CMD_PrepareForGameStart       = 96;    // 1. Server sends "prepare for start of game now"
    public static final Short PRE_CMD_Switch_To_PostMode        = 97;    // GameController switches to post mode operation    
    public static final Short POST_CMD_SynchronizeClient         = 98;    // 2. Server sends "synchorize" pulse to all clients
    public static final Short POST_CMD_GameStartPulse            = 99;    // 3. Server send "start game" interrupt
    
    
    /**
     * Command for POST Mode session - SEND thread
     */
    public static final Short POST_CMD_SEND_dxdy = 1000;
    public static final Short POST_CMD_SEND_score = 1001;
    public static final Short POST_CMD_SEND_life = 1002;
    
    /**
     * Command for POST Mode session - RECEIVE thread ie: Multicast thread
     */
    public static Short POST_CMD_MULTICAST_DXDY = 1;                // to send players dx dy
    public static Short POST_CMD_MULTICAST_FOOD_CONSUMED_BY = 2;    // server decides who has consumed the food
    public static Short POST_CMD_MULTICAST_CHANGE_LEVEL = 3;        // indicates that level needs to be changed now
    public static Short POST_CMD_MULTICAST_PLAYER_LEFT = 4;         // indicates the list of player(s) who has left the game
    public static Short POST_CMD_MULTICAST_CURRENT_FOOD = 5;        // indicates the current food for the game
    
    /** Creates a new instance of ISCP */
    public ISCP(InetAddress hostname, Integer port,GameController gc,iSnakeConfiguration conf) {
        // use this interface to send/receive objects
        // @TODO perform error checking on whether connection was successful or not
        this.clientNetworkInterface = new ClientNetworkInterface(port,hostname,this);
        this.gameController = gc;
        this.conf = conf;
        this.premodePacket = new premodeISCPpacket();
        this.postmodeMulticastPacket = new postmodeISCPpacketForMulticast();
        this.postmodeSendPacket = new postmodeISCPpacketForSend();
        this.setStopISCP(false);
        this.setPacketReceived(false);
    }
    
    public ISCP(Integer port,GameController gc,iSnakeConfiguration conf)   {
        this.clientNetworkInterface = new ClientNetworkInterface(port,this);
        this.gameController = gc;
        this.conf = conf;
        this.premodePacket = new premodeISCPpacket();
        this.postmodeMulticastPacket = new postmodeISCPpacketForMulticast();
        this.postmodeSendPacket = new postmodeISCPpacketForSend();
        this.setStopISCP(false);
        this.setPacketReceived(false);
    }
    
    /**
     * Send the local player preferences to the server
     * @param p PlayerPreferences object contaning the prefrences of local player
     */
    public void PRE_encodeAndSendLocalPrefrence(PlayerPreferences p) {
        premodeISCPpacket preISCP = new premodeISCPpacket(this.PRE_CMD_LocalPlayerPreference);
        preISCP.setLocalPlayerPereference(p);
        
        clientNetworkInterface.send(preISCP);
    }
    
    /**
     * Receives player preference sent by the server (which contains the snake color field set by the server
     * @return PlayerPreference object containing the actual preference (with snake color) set by the server
     */
    public PlayerPreferences PRE_receiveAndDecodeLocalPreferenceReply() {
        // Receive a object from clientNetwork Interface
        // Cast it to the type PlayerPreference
        // return it then
        return new PlayerPreferences();     // test
    }
    
    public premodeISCPpacket receiveAndDecode_preModeISCPpacket()   {
        // wait for packet from the server
        // decode (ie: cast the received object to desired type)
        // return the received object
        return new premodeISCPpacket();
    }
    
    public ClientNetworkInterface getClientNetworkInterface() {
        return clientNetworkInterface;
    }
    
    public void setClientNetworkInterface(ClientNetworkInterface clientNetworkInterface) {
        this.clientNetworkInterface = clientNetworkInterface;
    }
    
    public synchronized void run()   {
        while(!isStopISCP())   {
            if(isPacketReceived() && isPremode())  {
                // check if the received object is NULL or not, if object received is NULL, game server was closed
                if(objectReceived == null)    {
//                    clientNetworkInterface.setStop(true);
                    // give the player choice to reconnect to the server
                    gameController.status(getGameController().DEBUG_LEVEL_MESSAGE,"Client Network Interface closed unexpectedly.");
                } else    {
                    // packet received from Client Network Interface
                    // process the data receiving now according to the mode (pre/post)
                    if(isPremode()) {
                        this.premodePacket.clearAllMembers();
                        this.premodePacket = (premodeISCPpacket) objectReceived;

//                        gameController.status(gameController.DEBUG_LEVEL_MESSAGE,"Premode Command recived= "+premodePacket.getPremodeCommand().toString());
                        System.out.println("Premode Command recived= "+premodePacket.getPremodeCommand().toString());
                        if(premodePacket.getPremodeCommand().equals(this.PRE_CMD_OKJoinGame)) {
                            System.out.println("Step 1:");
                            gameController.status(getGameController().DEBUG_LEVEL_MESSAGE,"PRE_CMD_OKJoinGame");
                            // @TODO Inset continue or break after testing
                        }
                        
                        else if(premodePacket.getPremodeCommand().equals(this.PRE_CMD_ERRJoinGame))   {
                            gameController.status(gameController.DEBUG_LEVEL_ERROR,"Error Join game occured "+premodePacket.getPremodeCommand());
                        }
                        
                        else if(premodePacket.getPremodeCommand().equals(this.PRE_CMD_RemotePlayerPreference)) {
                            System.out.println("Step 2:");
                            gameController.status(getGameController().DEBUG_LEVEL_MESSAGE,"PRE_CMD_RemotePlayerPreference");

                            System.out.println("contents of gameSnakes sent by server: "+premodePacket.getRemotePlayerPreference().toString());
                            System.out.println("before addPlayersSnake() player snakes: "+gameController.getGameSnakes().toString());
                            
                            // received a vector containing the remote player preferences
//                            gameController.setPlayerPreferences(premodePacket.getRemotePlayerPreference());
                            
                            // create local snake objects to represent the remote player preferences                            
                            gameController.addPlayersSnake(premodePacket.getRemotePlayerPreference());
                            
                            // after receiving the packet set the packetReceived boolean variable to false
                            gameController.updateRemotePlayerList();
                            
//                            System.out.println("before addPlayersSnake() player snakes: "+gameController.getGameSnakes().toString());


//                            System.out.println("after addPlayersSnake() player snakes: "+gameController.getGameSnakes().toString());
                            
//                            System.out.println("contents of playersPreference: "+gameController.getPlayersPreference().toString());
                            // TLM: DONE
                            // TLM: DONE TESTING
                        }
                        
                        else if(premodePacket.getPremodeCommand().equals(this.PRE_CMD_ChatMessage))   {
                            gameController.status(gameController.DEBUG_LEVEL_MESSAGE,"Chat message received from "+gameController.getPlayerPreference(premodePacket.getChatMessageSenderId()));
                            gameController.updateChatPanel(premodePacket.getChatMessage(),premodePacket.getChatMessageSenderId());
                        }
                        
                        else if(premodePacket.getPremodeCommand().equals(this.PRE_CMD_PlayerThrownOut))   {
                            gameController.status(gameController.DEBUG_LEVEL_MESSAGE,"Could not join the game as the game server has already started the game. bye for now. try again :)");
                            setStopISCP(true);      // Stop this ISCP thread
                            // @TODO close the game after this
                        }

                        
                        /*
                         * Synchronization process Step 1:
                         */
                        // Step 1 signal
                        else if(premodePacket.getPremodeCommand().equals(this.PRE_CMD_PrepareForGameStart)) {
                            System.out.println("Step 3:");
                            //show user that  game start signal has been received
                            gameController.syncStep1();
                            // TLM: DONE
                        }
                        
                        // server sends wall coordinates
                        else if(premodePacket.getPremodeCommand().equals(this.PRE_CMD_WallCoordinate)) {
                            System.out.println("Step 4:");
                            // set walls in the gameController
                            // display "wall received"
                            // assuming that the wall coordinates are placed in xy[][]
                            gameController.status(gameController.DEBUG_LEVEL_MESSAGE,"Writing wall to gameFieldMatrix");
                            gameController.writeWallToGameFieldMatrix(premodePacket.getWallCoordinates());
                            // TLM: DONE
                            // TLM: TESTING REMAINING
                        }
                        
                        /*
                         * Synchronization process Step 2:
                         */
                        
                        // server sends each player's snake starting position
                        else if(premodePacket.getPremodeCommand().equals(ISCP.PRE_CMD_SnakeStartCoordinate)) {
                            System.out.println("Step 5:");
                            // set the starting position for each plyaer's snake
                            // display "receiving remote player's start position
                            gameController.status(gameController.DEBUG_LEVEL_MESSAGE,"Retriving remote player's start position");
                            gameController.setStartPositionForSnakes(premodePacket.getSnakeStartPosition());
                            // TLM: DONE
                            // TLM: TESTING REMAINING
                        }
                        
                        else if(premodePacket.getPremodeCommand().equals(this.PRE_CMD_GameSpeed)) {
                            System.out.println("Step 5.5:");
                            // set the starting position for each plyaer's snake
                            // display "receiving remote player's start position
                            gameController.status(gameController.DEBUG_LEVEL_MESSAGE,"Received game speed= "+premodePacket.getGameSpeed());
                            // TLM: DONE
                            // TLM: TESTING REMAINING
                        }

                        else if(premodePacket.getPremodeCommand().equals(this.PRE_CMD_Switch_To_PostMode)) {
                            System.out.println("Step 6:");
                            // set postmode = true, premode=false
                            // set iscp
                            setPremode(false);
                            setPostmode(true);
                            gameController.getGameFieldPanel().createPlayersSnakeColorList(gameController.getPlayersPreference());
                            gameController.syncStep2();
                            
                            // send reply to server that the client is now ready to receive the Third Sync Step
                            postmodeISCPpacketForSend postmodePacket = new postmodeISCPpacketForSend(POST_CMD_SynchronizeClient);
                            clientNetworkInterface.send(postmodePacket);
                            
                            gameController.getGameFieldPanel().updateGameField();
                            System.out.println("Switching mode from PREmode to POSTmode");
                        }

                        else    {
                            gameController.status(gameController.DEBUG_LEVEL_ERROR,"Unknown cmd from server: "+premodePacket.getPremodeCommand());
                        }
                        this.setPacketReceived(false);
                        notifyAll();
                    }
                }
            } 
            else
            {
                try {
//                    gameController.status(gameController.DEBUG_LEVEL_MESSAGE,"Entering wait mode");
                    wait();    // wait until next data arrives
//                    gameController.status(gameController.DEBUG_LEVEL_MESSAGE,"Exiting wait mode");
                } catch(InterruptedException ex)  {
                    ex.printStackTrace();
                }
                
            }
        }
    }
    
    /**
     * Fuction invoked by the ClientNetworkInteface to forward data received, from the server, to ISCP
     */
    public synchronized void forwardReceivedISCPpacket(Object obj) {
//        gameController.status(gameController.DEBUG_LEVEL_MESSAGE,"Forward packet received");
        while(true){
//            System.out.println("inside while(true)");
            if(!isPacketReceived()){
                gameController.status(gameController.DEBUG_LEVEL_MESSAGE,"Object Received "+obj.toString());
//                System.out.println("isPacketReceived()+"+isPacketReceived());
                this.objectReceived = obj;
                this.setPacketReceived(true);
                break;
            } else {
                try {
                    wait();
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        }
        notifyAll();
    }
    
    public premodeISCPpacket getPremodePacket() {
        return premodePacket;
    }
    
    public void setPremodePacket(premodeISCPpacket premodePacket) {
        this.premodePacket = premodePacket;
    }
    
    public postmodeISCPpacketForSend getPostmodeSendPacket() {
        return postmodeSendPacket;
    }
    
    public void setPostmodeSendPacket(postmodeISCPpacketForSend postmodeSendPacket) {
        this.postmodeSendPacket = postmodeSendPacket;
    }
    
    public postmodeISCPpacketForMulticast getPostmodeMulticastPacket() {
        return postmodeMulticastPacket;
    }
    
    public void setPostmodeMulticastPacket(postmodeISCPpacketForMulticast postmodeMulticastPacket) {
        this.postmodeMulticastPacket = postmodeMulticastPacket;
    }
    
    public boolean isPacketReceived() {
        return packetReceived;
    }
    
    public void setPacketReceived(boolean packetReceived) {
//        System.out.println("setting packet received to: "+packetReceived);
        this.packetReceived = packetReceived;
    }
    
    public boolean isPremode() {
        return premode;
    }
    
    public void setPremode(boolean premode) {
        this.premode = premode;
    }
    
    public boolean isPostmode() {
        return postmode;
    }
    
    public void setPostmode(boolean postmode) {
        this.postmode = postmode;
    }
    
    public iSnakeConfiguration getConf() {
        return conf;
    }
    
    public GameController getGameController() {
        return gameController;
    }

    public boolean isStopISCP() {
        return stopISCP;
    }

    public void setStopISCP(boolean stopISCP) {
        this.stopISCP = stopISCP;
    }
}